#include "../Headers/identity.h"

/* REFERENCE
#define TIME_ERROR -2
#define TIME_INITIAL -1
#define TIME_SCOPE 0

#define GET_TIME_OF_LAST -1

struct ident_list {
    cl_time_t time;
    cluster* value;
    struct ident_list* new_scope;
    struct ident_list* new_scope_back;
    struct ident_list* next;
    struct ident_list* next_back;
};
*/

ident_list*
cl_new_ident_list() {
    ident_list* list = (ident_list*)malloc(sizeof(ident_list));

    list->time = TIME_ERROR;
    list->value = NULL;
    list->new_scope = NULL;
    list->new_scope_back = NULL;
    list->next = NULL;
    list->next_back = NULL;

    return list;
}

/*! @TODO Implement this.
 */
cluster*
_get_placeholder_cl_from_ident_list(ident_list* cursor) {
    cluster* retval;
    if (cursor->time == TIME_SCOPE) {
        retval = cursor->value;
    } else {
        retval = _get_placeholder_cl_from_ident_list(cursor->next_back);
    }
    return retval;
}

/*!
 * @param list
 *  This is expected to be somewhere in the middle of the list or at the end.
 * @param registration_flag
 *  This flag will signal whether or not to unregister the placeholder clusters and
 *  the primordial cluster. If 0, then unregistering won't happen. Otherwise, they
 *  will all have their reference counters decremented.
 */
cluster*
_get_cl_from_ident_list(ident_list* list, int registration_flag) {
    cluster* retval = NULL;
    if (list == NULL) {
        return retval;
    } else if (list->time == TIME_INITIAL) {
        retval = list->value;
        // Don't register here, because this is a placeholder.
    } else if (list->time == TIME_ERROR) {
        assert(0);
    } else if (list->time == TIME_SCOPE) {
        list = list->new_scope_back;
        retval = _get_cl_from_ident_list(list, registration_flag);
        if (registration_flag == UN_REGISTER) {
            cl_unregister(_get_placeholder_cl_from_ident_list(list));
        } else if (registration_flag == DO_REGISTER) {
            cl_register(_get_placeholder_cl_from_ident_list(list));
        }
    } else {
        list = list->next_back;
        retval = _get_cl_from_ident_list(list, registration_flag);
    }
    return retval;
}

cluster*
_get_ident_cl(ident_list* list) {
    return _get_cl_from_ident_list(list, DO_NOT_REGISTER);
}

void
_register_ident_list_upward(ident_list* list) {
    _get_cl_from_ident_list(list, DO_REGISTER);
}

void
_unregister_ident_list_upward(ident_list* list) {
    _get_cl_from_ident_list(list, UN_REGISTER);
}

/*!
 * @param cl
 *  This is the cluster whose identity is being set.
 * @param time
 *  The time at which this change will take place.
 * @param value
 * The value, calculated or not, of cl at this time.
 */
void
cl_set_identity(cluster* cl, cl_time_t time, cluster* value) {
    ident_list *cursor = cl->identity;
    if (cursor == NULL) {
        cl->identity = cl_new_ident_list();
        cursor = cl->identity;
        cursor->time = TIME_INITIAL;
        cursor->value = cl;  // Special case so that we can get the primal cluster from any node.

        // cursor->next now equals NULL, so the initialization will be
        // picked up below.
    } else {
        while (cursor->next != NULL && time <= cursor->next->time) {
            cursor = cursor->next;
        }
    }

    if (cursor->next == NULL) {  // Normal case; the parser identified a statement that might change
                                 // the value of the cluster.
        cursor->next = cl_new_ident_list();
        cursor->next->next_back = cursor;
        cursor = cursor->next;
        cursor->time = time;
        cursor->value = value;
        cursor->new_scope = cl_new_ident_list();  // This might be removed after cl_get_identity reads
                                                  // the value.
        cursor->new_scope->new_scope_back = cursor;
        cursor->new_scope->time = TIME_SCOPE;

        //! @TODO Investigate exactly what is happening here. When ther grammar calls a function, it will
        // create slots in the dependencies list. Those slots will have the local names for variables,
        // but the ultimate value will correspond to a cluster in a different scope. The parameter slots
        // will have the ident_list that has a head flag of TIME_SCOPE. However, in order to connect that,
        // we will either need to return the value here, which is likely more efficient, or else have a
        // different function that finds the value, which is likely more readable/debuggable. Furthermore,
        // when we do this, we need to use cl_register on something... After all, the cluster may not be
        // garbage collected while something might depend on it. So, we need to use register on the head cl
        // every time we split off a new scope, and we need to unregister every time that new scope is resolved.
        _register_ident_list_upward(cursor);  //! This shouldn't register the new placeholder cluster.
    } else if (time < cursor->next->time) {  // I can't think of a situation at the moment where
                                             //this will happen.
        assert(0);
    } else if (time == cursor->next->time) {  // This scenario will happen when cl_get_identity
                                              // has retreived a value. The result will be to clear
                                              // the new_scope list.
        cursor->value = value;
        // The un_register will be called in the function, as close to the free() as possible.
        cl_empty_ident_list(&cursor->new_scope);
    } else {
        assert(0);
    }
}

cluster*
cl_get_identity(ident_list* list, cl_time_t time) {
    cluster* cl = NULL;  // This is a temp variable... I'll edit it out once I've got the code
                         // working.
    cluster* retval = NULL;
    ident_list* cursor = list;
    assert(list->time != TIME_ERROR);

    while (cursor->next != NULL && (time == GET_TIME_OF_LAST || time < cursor->next->time))
        cursor = cursor->next;

    // cursor->value is now pointing at the correct cluster. However, the cluster might or might
    // not be evaluated.
    if (cursor->new_scope != NULL) {  // The value has not been retreived before.
        cl = _get_ident_cl(list);
        //! @TODO Set the job in the cluster cl, then wait for the callback.
        //set_job(cl)
        //retval = callback
        cl_set_identity(cl, time, retval);  // So that future requests don't trigger the
                                               // processing.
    } else {  // The value has previously been retrieved.
        retval = cursor->value;
    }

    return retval;
}

/*! @TODO
 *  I'm not very confident that the recursion order is correct. I think that the unregistering
 *  will happen in the correct order (and in the correct amount), but it needs to be tested.
 */
void
cl_empty_ident_list(ident_list** list_loc) {
    ident_list *cursor, *marker, *scope;
    cursor = *list_loc;
    if (cursor == NULL) return;
    marker = cursor->next;
    assert(cursor->new_scope == NULL);

    while (marker != NULL) {
        cursor = marker;
        marker = cursor->next;
        scope = cursor->new_scope;

        free(cursor);
        if (scope != NULL) {
            cl_empty_ident_list(&scope);
        }
    }

    _unregister_ident_list_upward(cursor);
    free(cursor);
    *list_loc = NULL;
}
